package host.discoveryProtocol;

import homeMadeExceptions.NotYetImplementedException;
import host.DiscoveryNetworkSocket;

import java.util.HashMap;
import java.util.Vector;

import datatypes.BluetoothDeviceAddress;
import datatypes.Host;
import datatypes.Message;

public class FloodingRequestDiscovery extends DiscoveryNetworkSocket {

	private final String ASK_FOR_SERVICE_STRING = "ASK_FOR_SERVICE";
	private final String FIND_SERVICE_STRING = "FIND_SERVICE";
	private final String GET_ALL_AVAILABLE_SERVICES_STRING = "GET_ALL_AVAILABLE_SERVICE";
	private final String REPLY_FOR_ASK_SERVICE = "A_REPLY";
	private final String REPLY_FOR_FIND_SERVICE = "F_REPLY";
	private final String REPLY_FOR_GET_ALL = "G_REPLY";
	
	//the host this socket belongs to
	private Host host = null;
	private boolean discoveryMechanismIsOn = false;
	
	private RoutingTable routingTable = new RoutingTable();
	
	private enum requestState {
		OPEN, CLOSED, FAILED;
	}
	
	//sendedMessages
	private HashMap<Integer, requestState> messageIDtoStateMap = new HashMap<Integer, requestState>();
	//receivedMessages
	private HashMap<Integer, requestState> messageIDtoAnswerMap = new HashMap<Integer, requestState>();
	
	
	private FloodingRequestDiscovery(Host host) {
		super(host);
		this.host = host;
	}
	
	public static FloodingRequestDiscovery getSocket(Host host){
		return new FloodingRequestDiscovery(host);
	}

	@Override
	public void enableDiscoveryMechanism() {
		super.enableDiscoveryMechanism();
		this.discoveryMechanismIsOn = true;
	}

	@Override
	public void disableDiscoveryMechanism() {
		super.disableDiscoveryMechanism();
		this.discoveryMechanismIsOn = false;
	}

	/**
	 * asynchonous --> void (normaly returns a Vector with the Services of the Remote host as String)
	 */
	public void askHostForServices(BluetoothDeviceAddress target) {
		String messageString = this.ASK_FOR_SERVICE_STRING;
		Vector<Object> messageContent = new Vector<Object>();
		messageContent.add(messageString);
		Message msg = new Message (messageContent, true);
		this.invokeSendMessage(msg, target);
	}
	/**
	 * asynchonous --> void (normaly returns a Vector with the Provider of the Remote host as BluetoothDeviceAddress)
	 */
	public void findSpecificService(String serviceDescription) {
		String messageString = this.FIND_SERVICE_STRING;
		Vector<Object> messageContent = new Vector<Object>();
		messageContent.add(messageString);
		messageContent.add(serviceDescription);
		Message msg = new Message (messageContent, true);
		this.invokeSendMessage(msg, null);
	}

	/**
	 * asynchonous --> void (normally returns a Vector with the available Services as Strings)
	 */
	public void getAllAvailableServices() {
		String messageString = this.GET_ALL_AVAILABLE_SERVICES_STRING;
		Vector<Object> messageContent = new Vector<Object>();
		messageContent.add(messageString);
		Message msg = new Message (messageContent, true);
		this.invokeSendMessage(msg, null);
	}

	public void publishLocalServices(String newService) {
		this.host.getServices().add(newService);
	}

	public void updateLocalService (String oldService, String newService){
		this.host.getServices().remove(oldService);
		this.host.getServices().add(newService);
	}
	
	public void deleteLocalService (String service){
		this.host.getServices().remove(service);
	}
	private void invokeAnswerMessage(Message msg, BluetoothDeviceAddress target) {		
		this.messageIDtoAnswerMap.put(msg.getMsgID_asInteger(), requestState.OPEN);
		this.invokeSendMessage(msg, target);
	}
	private void invokeForwardMessage(Message msg, BluetoothDeviceAddress target) {		
		this.messageIDtoAnswerMap.put(msg.getMsgID_asInteger(), requestState.CLOSED);
		this.invokeSendMessage(msg, target);
	}
	@Override
	protected void invokeSendMessage(Message msg, BluetoothDeviceAddress target) {
		this.messageIDtoStateMap.put(msg.getMsgID_asInteger(), requestState.OPEN);
		super.invokeSendMessage(msg, target);
	}

	@Override
	public void notificationAPI() {
		//nothing to do within this protocol
	}

	@Override
	public void onConnectionEstablished(BluetoothDeviceAddress remoteHost) {
		this.routingTable.addHostToAddressList(remoteHost);
	}

	@Override
	public void onConnectionLost(BluetoothDeviceAddress remoteHost) {
		this.routingTable.removeHostFromAddressList(remoteHost);
	}

	@Override
	public void onMessageACK(Message msg, BluetoothDeviceAddress target) {
		if (this.messageIDtoStateMap.get(msg.getMsgID_asInteger())!= null){
			this.messageIDtoStateMap.remove(msg.getMsgID_asInteger());
			this.messageIDtoStateMap.put(msg.getMsgID_asInteger(), requestState.CLOSED);
		}
		if (this.messageIDtoAnswerMap.get(msg.getMsgID_asInteger())!= null){
			this.messageIDtoAnswerMap.remove(msg.getMsgID_asInteger());
			this.messageIDtoAnswerMap.put(msg.getMsgID_asInteger(), requestState.CLOSED);
		}
	}

	@Override
	public void onMessageLost(Message msg, BluetoothDeviceAddress target) throws NotYetImplementedException {
		if (this.messageIDtoStateMap.get(msg.getMsgID_asInteger())!= null){
			this.messageIDtoStateMap.remove(msg.getMsgID_asInteger());
			this.messageIDtoStateMap.put(msg.getMsgID_asInteger(), requestState.FAILED);
		}
		if (this.messageIDtoAnswerMap.get(msg.getMsgID_asInteger())!= null){
			this.messageIDtoAnswerMap.remove(msg.getMsgID_asInteger());
			this.messageIDtoAnswerMap.put(msg.getMsgID_asInteger(), requestState.FAILED);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void receiveMessage(Message msg, BluetoothDeviceAddress sender) {
		//Handle incoming Message
		if (msg.isDiscoveryMessage()){
			Vector<Object> content = (Vector<Object>) msg.getContent();
			String messageString  = (String)content.get(0);
			if (messageString.equals(this.ASK_FOR_SERVICE_STRING)){
				//only answer if not done before
				if (this.messageIDtoAnswerMap.get(msg.getMsgID_asInteger()) == null ||
						this.messageIDtoStateMap.get(msg.getMsgID_asInteger()) == requestState.FAILED){
					replyToAskForService (sender);
					//forward to other neighbours
					this.invokeForwardMessage(msg, null);
				}
			} else if (messageString.equals(this.FIND_SERVICE_STRING)){
				//only answer if not done before
				if (this.messageIDtoAnswerMap.get(msg.getMsgID_asInteger()) == null ||
						this.messageIDtoStateMap.get(msg.getMsgID_asInteger()) == requestState.FAILED){
					if (!replyToFindService (sender, (String)content.get(1))){
						//If Service was not found forward
						this.invokeForwardMessage(msg, null);
					}
				}
			} else if (messageString.equals(this.GET_ALL_AVAILABLE_SERVICES_STRING)){
				//only answer if not done before
				if (this.messageIDtoAnswerMap.get(msg.getMsgID_asInteger()) == null ||
						this.messageIDtoStateMap.get(msg.getMsgID_asInteger()) == requestState.FAILED){
					replyToGetAllAvailableServices (sender);
					this.invokeForwardMessage(msg, null);
				}
			} 
			//REPLYS
			else if (messageString.equals(this.REPLY_FOR_ASK_SERVICE)){
				//sender + services of sender
				processResultAskForService(sender, (Vector<String>)content.get(1));
			}else if (messageString.equals(this.REPLY_FOR_FIND_SERVICE)){
				//sender (=service provider) + requested service
				processResultFindService(sender, (String)content.get(1));
			}else if (messageString.equals(this.REPLY_FOR_GET_ALL)){
				//sender + services of sender
				processResultGetAllServices(sender, (Vector<String>)content.get(1));				
			}
		}
	}

	

	
	private void replyToAskForService(BluetoothDeviceAddress sender) {
		String messageString = this.REPLY_FOR_ASK_SERVICE;
		Vector<Object> messageContent = new Vector<Object>();
		messageContent.add(messageString);
		messageContent.add(this.host.getServices());
		Message msg = new Message (messageContent, true);
		this.invokeAnswerMessage(msg, sender);
	}
	
	/**
	 * 
	 * @param sender
	 * @param service
	 * @return true if Service was found (false otherwise)
	 */
	private boolean replyToFindService(BluetoothDeviceAddress sender, String service) {
			boolean imaProvider = false;
			for (String serv : this.host.getServices()){
				if (serv.equals(service)){
					imaProvider = true;
				}
			}
			if (imaProvider){
				String messageString = this.REPLY_FOR_FIND_SERVICE;
				Vector<Object> messageContent = new Vector<Object>();
				messageContent.add(messageString);
				messageContent.add(service);
				Message msg = new Message (messageContent, true);
				this.invokeAnswerMessage(msg, sender);	
				return true;
			} else {
				return false;
			}
		}
	
	private void replyToGetAllAvailableServices(BluetoothDeviceAddress sender) {
		String messageString = this.REPLY_FOR_GET_ALL;
		Vector<Object> messageContent = new Vector<Object>();
		messageContent.add(messageString);
		messageContent.add(this.host.getServices());
		Message msg = new Message (messageContent, true);
		this.invokeAnswerMessage(msg, sender);
	}

	private class RoutingTable {
		private Vector<BluetoothDeviceAddress> addressList = new Vector<BluetoothDeviceAddress>(); 
		
		public Vector<BluetoothDeviceAddress> getAddressList (){
			return this.addressList;
		}
		
		public void addHostToAddressList (BluetoothDeviceAddress address){
			if (!addressList.contains(address)){
				addressList.add(address);
			}
		}
		
		public void removeHostFromAddressList (BluetoothDeviceAddress address){
			if (addressList.contains(address)){
				addressList.remove(address);
			}
		}
	}
	
	private void processResultAskForService(BluetoothDeviceAddress sender, Vector<String> vector) {
		// TODO Auto-generated method stub
		System.out.println("FloodingRequestDiscovery: ask for Service at " +sender.getAddress() + " processed");
	}
	
	private void processResultFindService(BluetoothDeviceAddress sender, String service) {
		// TODO Auto-generated method stub
		System.out.println("FloodingRequestDiscovery: find Service:" + service + "at: " +sender.getAddress() + " processed");		
	}

	private void processResultGetAllServices(BluetoothDeviceAddress sender, Vector<String> vector) {
		// TODO Auto-generated method stub
		System.out.println("FloodingRequestDiscovery: find all Services, answer from: " +sender.getAddress() + " processed");				
	}

}
